// =================================================================                                                                   
// Copyright (C) 2011-2013 Pierre Lison (plison@ifi.uio.no)                                                                            
// Permission is hereby granted, free of charge, to any person 
// obtaining a copy of this software and associated documentation 
// files (the "Software"), to deal in the Software without restriction, 
// including without limitation the rights to use, copy, modify, merge, 
// publish, distribute, sublicense, and/or sell copies of the Software, 
// and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:
// The above copyright notice and this permission notice shall be 
// included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// =================================================================                                                                   
package uk.ac.shef.speech_r25;

import org.mechio.api.speech.messaging.RemoteSpeechServiceClient;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import marytts.LocalMaryInterface;
import marytts.MaryInterface;
import marytts.exceptions.MaryConfigurationException;
import marytts.util.data.audio.AudioPlayer;
import org.mechio.api.animation.Animation;

import org.mechio.api.animation.messaging.RemoteAnimationPlayerClient;
import org.mechio.api.animation.player.AnimationJob;
import org.mechio.api.motion.Robot.RobotPositionMap;
import org.mechio.api.motion.messaging.RemoteRobot;
import org.mechio.api.speech.utils.DefaultSpeechJob;
import org.mechio.client.basic.MechIO;
import org.mechio.client.basic.UserSettings;

/**
 * Simple example of a synchronous module for the domain specified in
 * domains/examples/example-step-by-step.xml.
 *
 * <p>
 * The example creates a visual grid of size GRID_SIZE and updates the position
 * of the agent in accordance with the movements.
 *
 * @author Pierre Lison (plison@ifi.uio.no)
 * @version $Date:: 2014-04-16 17:34:31 #$
 */
public class Robot {

    // logger
    // start the agent in the middle of the grid
    RemoteSpeechServiceClient mySpeaker;
    RemoteAnimationPlayerClient animPlayer;
    RemoteRobot myRobot;
    RobotPositionMap storedPositions;
    boolean robotActive;
    HashMap<String, Animation> animations;
    public DefaultSpeechJob currentSpeechJob = null;
    private boolean robotSpeechPendingComplete;
    private long endOfSpeech;
    LinkedList<String> speechQueue;
    LinkedList<String> animationQueue;
    MaryInterface marytts;
    AudioPlayer audioPlayer;

    public Robot() {
        Utils utils = new Utils();
        HashMap<String, String> configs = utils.readConfig();

        robotActive = Boolean.parseBoolean(configs.get("robot-active"));
        speechQueue = new LinkedList<String>();
        animationQueue = new LinkedList<String>();
        if (robotActive) {
            Animation happyAnim = MechIO.loadAnimation("animations/victory.xml");
            Animation sadAnim = MechIO.loadAnimation("animations/disappointed.xml");
            Animation waveAnim = MechIO.loadAnimation("animations/robokind-wave.xml");
            Animation leftAnim = MechIO.loadAnimation("animations/left-arm.xml");
            Animation rightAnim = MechIO.loadAnimation("animations/right-arm.xml");
            animations = new HashMap<String, Animation>();

            animations.put("happy", happyAnim);
            animations.put("sad", sadAnim);
            animations.put("wave", waveAnim);
            animations.put("left_arm", leftAnim);
            animations.put("right_arm", rightAnim);

            String robotID = "myRobot";
            String robotIP = configs.get("ip");
            UserSettings.setSpeechAddress(robotIP);
            UserSettings.setRobotId(robotID);
            UserSettings.setRobotAddress(robotIP);
            UserSettings.setAnimationAddress(robotIP);
            myRobot = MechIO.connectRobot();
            animPlayer = MechIO.connectAnimationPlayer();
            mySpeaker = MechIO.connectSpeechService();
        } else {
            try {
                marytts = new LocalMaryInterface();
                Set<String> voices = marytts.getAvailableVoices();
                marytts.setVoice(voices.iterator().next());
            } catch (MaryConfigurationException ex) {
                Logger.getLogger(Robot.class.getName()).log(Level.SEVERE, null, ex);
            }

        }
    }

    public void addSpeech(String text) {
        speechQueue.add(text);
    }

    void flushQueue() {
        if (!speechQueue.isEmpty()) {
            if (speechFinished()) {
                String text = speechQueue.poll();
                speak(text);
            }
        }
        if (!animationQueue.isEmpty()) {
            if (animationFinished()) {
                String anim = animationQueue.poll();
                playAnimation(anim);
            }
        }
    }

    boolean animationFinished() {
        if (!robotActive) {
            return true;
        }
        boolean finished = false;
        List<AnimationJob> anims = animPlayer.getCurrentAnimations();
        if (!anims.isEmpty()) {
            AnimationJob head = anims.get(0);
            if (head.getRemainingTime(System.currentTimeMillis()) <= 0) {
                finished = true;
            }
        } else {
            finished = true;
        }
        return finished;
    }

    private void speak(String text) {
        if (robotActive) {
            currentSpeechJob = (DefaultSpeechJob) mySpeaker.speak(text);
        } else {
            //System.out.println("Robot speaking "+text);
            localSpeak(text);
        }
    }

    private void playAnimation(String name) {
        if (robotActive) {

            if (name.equals("Default")) {
                RobotPositionMap map = myRobot.getDefaultPositions();
                myRobot.move(map, 1000);
            } else {
                Animation anim = animations.get(name);
                animPlayer.playAnimation(anim);
            }
        } else {
            //System.out.println("Robot anim " + name);
            localSpeak("anim " + name);
        }

    }

    void addAnimation(String name) {
        animationQueue.add(name);
    }

    boolean hasAnimation(String text) {
        return animations.containsKey(text);
    }

    boolean speechFinished() {
        long now = System.currentTimeMillis();
        if (robotActive) {
            if (currentSpeechJob == null) {
                if (robotSpeechPendingComplete) {
                    if (now > endOfSpeech) {
                        robotSpeechPendingComplete = false;
                        return true;
                    } else {

                        return false;
                    }
                } else {
                    return true;
                }
            } else {
                boolean ret = (currentSpeechJob.getStatus() == DefaultSpeechJob.COMPLETE);
                if (ret) {
                    this.robotSpeechPendingComplete = true;
                    currentSpeechJob = null;
                    endOfSpeech = now + 500;

                }
                return false;

            }
        } else {

            return true;
        }
    }

    void localSpeak(String text) {
        try {

            AudioInputStream audio = marytts.generateAudio(text);
            AudioFormat format = audio.getFormat();
            long audioFileLength = audio.getFrameLength();

            //int frameSize = format.getFrameSize();
            float frameRate = format.getFrameRate();
            float durationInSeconds = (audioFileLength / frameRate);
            long length = (long) (durationInSeconds * 1000);
            audioPlayer = new AudioPlayer(audio);
            audioPlayer.start();

            // player.join();
            System.out.println("length = " + length);
            long now = System.currentTimeMillis();
            endOfSpeech = now + length + 500;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void setDefaultPositions() {
       RobotPositionMap map = myRobot.getDefaultPositions();
      myRobot.move(map, 1000);
    }

}
